Skip to content

release: Zig 0.17 migration with faster, parity-guarded retrieval - #674

Merged
justrach merged 2 commits into
release/0.2.5829from
perf/zig-0.17-migration-673
Jul 11, 2026
Merged

release: Zig 0.17 migration with faster, parity-guarded retrieval#674
justrach merged 2 commits into
release/0.2.5829from
perf/zig-0.17-migration-673

Conversation

@justrach

Copy link
Copy Markdown
Owner

Closes #673.

Summary

Migrate the current 0.2.5829 release train from Zig 0.16.0 to the exact tested 0.17.0-dev.813+2153f8143 snapshot, then remove the migration's cold-path regressions without changing retrieval semantics.

This PR:

  • pins and verifies Zig 0.17 in the package, benchmark CI, and release workflow;
  • keeps transition benchmark CI honest by running the base and head with each revision's declared 0.16/0.17 compiler (base failure no longer becomes a successful "skipped" comparison);
  • vendors the 336KB Zig-0.17-compatible nanoregex source with MIT license, exact upstream commit, and parity fixtures;
  • replaces the old mcp-zig dependency with the small protocol-neutral JSON helpers actually used by codedb;
  • optimizes exact-word deduplication, Tier-0 document aggregation, release startup/argv, word-index lock lifetime, worker directory reuse, and rolling trigram extraction;
  • adds differential parity guards for optimized versus defensive posting paths, canonical versus rolling trigram masks, lazy word-index rebuilding, persisted/mmap ordering, and MCP JSON framing/escaping;
  • synchronizes build.zig.zon, src/release_info.zig, npm, and CHANGELOG.md at 0.2.5829;
  • documents the complete migration, benchmark method, wins, caveats, and remaining bottleneck in docs/zig-0.17-migration.md and the release changelog.

Retrieval behavior / quality

No scoring, ranking, tokenization, parsing, filtering, result-cap, tie-break, or rendering formula changes in this pass.

The fast paths are guarded by invariants and retain the previous behavior as fallback:

  • pair-sorted exact-word postings compact adjacent duplicates linearly; any ordering break reruns full hash deduplication and preserves first-occurrence order;
  • nondecreasing Tier-0 postings aggregate contiguous document runs without a map; fragmented/nonmonotonic postings use the previous slots/hash path;
  • rolling trigram extraction is compared trigram-by-trigram and path-by-path against canonical extraction, including loc_mask and next_mask;
  • direct A/B query hit counts match the Zig 0.16 baseline;
  • grouped and semantically equivalent fragmented Tier-0 lists produce identical ordered paths, line numbers/text, scores, and truncation.

Red → green evidence

Before

The compatibility repro under the pinned Zig 0.17 snapshot was zig build test; the pre-migration tree failed on removed build APIs, repetition syntax, bufPrintZ, direct I/O-vtable access, unsupported wasm64 atomics, and incompatible dependency build/source code.

After the tree compiled, the initial same-machine performance A/B was still a regression:

  • direct initial indexing, 15 alternating runs on a stable 693-file corpus: Zig 0.16 median 187ms, initial Zig 0.17 median 188ms;
  • cold in-process CLI search with isolated HOME, daemon off, telemetry off: Zig 0.16 114.3 ± 2.2ms, initial Zig 0.17 116.4 ± 3.3ms;
  • --version: approximately 3% slower.

After

Final immutable-corpus A/B:

  • cold single-token CLI: 127.0 ± 26.0ms → 105.7 ± 10.7ms (1.20×, −16.8%);
  • warm snapshot CLI: 117.2 ± 27.8ms → 102.8 ± 13.3ms (1.14×, −12.3%);
  • exact-word query geometric mean: 3.217× faster;
  • all direct queries geometric mean: 1.405× faster;
  • search-only geometric mean: 1.007×; symbol geometric mean: 1.013×;
  • all direct query hit counts identical.

The known remaining gap is generic full parse/serial-commit initial scan: 166ms → 169ms median (1.8% slower). Production cold single-token search uses the optimized trigram scan path and is materially faster. Closing generic scan needs a larger parser/commit ownership redesign and is intentionally not mixed into this behavior-preserving release.

Benchmark methodology

  • Layer: native direct query/index microbenchmarks plus end-to-end CLI process latency.
  • Machine: Apple Mac Studio Mac15,14, Apple M3 Ultra, arm64, macOS 26.5.1 (25F80).
  • Build: native ReleaseFast for both revisions.
  • Baseline: clean 428d8df, Zig 0.16.0.
  • Candidate: this branch, Zig 0.17.0-dev.813+2153f8143.
  • Corpus: immutable 631-file git archive of 428d8df, stored outside the working repository.
  • Direct runs: 20 alternating A/B pairs, 200 iterations per query; reported direct values are medians/geometric means.
  • CLI runs: Hyperfine, 5 warmups, 40 measured runs, separate isolated homes/caches, CODEDB_ALLOW_TEMP=1, CODEDB_NO_CLI_DAEMON=1, CODEDB_NO_TELEMETRY=1.
  • Cache state: cold CLI uses fresh isolated cache/home; warm CLI uses a prepared snapshot; direct warm queries run in-process after index construction.
  • Hyperfine reported outliers for both CLI samples, so mean ± standard deviation is shown and no single run is presented as representative.

The A/B invocation shape was:

env CODEDB_ALLOW_TEMP=1 CODEDB_NO_CLI_DAEMON=1 CODEDB_NO_TELEMETRY=1 \
  hyperfine --warmup 5 --runs 40 \
  --prepare 'rm -rf "$BASE_HOME" "$CURRENT_HOME"; mkdir -p "$BASE_HOME" "$CURRENT_HOME"' \
  'HOME="$BASE_HOME" "$BASE_BIN" "$CORPUS" search config' \
  'HOME="$CURRENT_HOME" "$CURRENT_BIN" "$CORPUS" search config'

The direct harness alternated baseline/candidate order per pair and asserted hit counts before recording timing. Full tables and methodology are in CHANGELOG.md and docs/zig-0.17-migration.md.

Verification

Final tree:

rm -rf .zig-cache && zig build test --summary all
  23/23 build steps succeeded
  892/896 tests passed; 4 platform skips

zig build test-index test-explore test-search --summary all
  index 181/181
  explore 132/132
  search 113/113

zig build test-mcp --summary all
  152/156 passed; 4 platform skips

zig build -Doptimize=ReleaseFast
zig build wasm
python3 scripts/e2e_mcp_test.py --binary zig-out/bin/codedb --project "$PWD"
  20/20 passed

cd vendor/nanoregex
zig build test-all --summary all
  405/405 passed
zig build parity
  27 passed, 0 failed, 0 skipped

git diff --check
  clean

Modified Zig files pass zig fmt/the configured codedb-pro lint. npm/package.json parses as valid JSON. The benchmark workflow parses as YAML, validates the declared base/head compiler selection, verifies both downloaded archives by SHA-256, and fails closed when base benchmarking/comparison fails.

Files / subsystems

  • build/release/CI: build.zig, build.zig.zon, .github/workflows/*
  • platform/runtime compatibility: src/cio.zig, main/CLI/server/MCP/telemetry/snapshot/WASM modules
  • retrieval/performance: src/index.zig, src/explore.zig, src/watcher.zig
  • protocol dependency replacement: src/mcp_json.zig
  • parity/regression coverage: index/explore/search/MCP and neighboring suites
  • vendored dependency: vendor/nanoregex/
  • release docs/version surfaces: changelog, migration guide, README, npm, release info

Scope and repository hygiene

This exceeds the default 500-line recommendation because the compiler migration must update the build graph and every compiled target atomically, and the 336KB vendored dependency contributes most of the added lines. Splitting the dependency or compatibility edits would leave intermediate commits unable to build under the release compiler; the performance changes are included because the standing release requirement was to beat, not regress from, Zig 0.16 while preserving 1:1 retrieval.

  • Branch was created directly from current origin/release/0.2.5829 at 428d8df (0 commits divergent at creation). It was not rebased onto main because this repository's PR guard requires the active release/* base.
  • Generated .zig-cache, .zig-global-cache, zig-out, WASM output, vendor caches, Graff/harness state, and JSONL traces are not committed.
  • No generated binary artifacts are included.
  • build.zig.zon changes intentionally replace remote dependencies with reproducible local sources; there is no unrelated lockfile churn.
  • Benchmark CI and release workflow changed; the benchmark implementation/data format did not.
  • The persisted trigram files remain individually tmp+rename replaced; pair publication was already non-transactional in the base and is now documented accurately. This PR writes/flushed both temporaries before either rename but does not claim a transactional pair publication.

Remaining non-blocking risk

Optimized native builds now use the process default stack for the measured ~190KB main frame; Debug retains the 64MB worker trampoline. Mainstream native stacks have ample headroom, but Windows ReleaseFast/ReleaseSmall smoke coverage remains a useful follow-up for constrained targets.

Contribution confirmation

This submission is issue-linked, targets the active release branch, includes before/after evidence and neighboring non-regression checks, discloses the benchmark environment/cache state/run count/aggregation, excludes generated artifacts, explains the unavoidable large scope, and follows CONTRIBUTING.md.

Pin and verify the Zig 0.17 release toolchain, vendor the compatible nanoregex source, and internalize the small MCP JSON helpers. Keep the benchmark gate comparing each revision with its declared compiler.

Optimize exact-word deduplication, Tier-0 aggregation, release startup, argv parsing, worker directory reuse, and rolling trigram extraction while retaining defensive fallbacks and adding differential retrieval tests.

Document the migration, same-machine Zig 0.16 A/B results, full verification, and the remaining generic initial-scan gap.

Closes #673
@github-actions

Copy link
Copy Markdown

Benchmark Regression Report

Thresholds: 10.00% and 50,000 ns absolute delta

NOISE means the percentage threshold was exceeded, but the absolute delta was too small to fail CI.

Tool Base (ns) Head (ns) Delta Abs Delta (ns) Status
codedb_bundle 63928 61902 -3.17% -2026 OK
codedb_changes 12685 11480 -9.50% -1205 OK
codedb_context 302401 314294 +3.93% +11893 OK
codedb_deps 255 325 +27.45% +70 NOISE
codedb_edit 45027 75464 +67.60% +30437 NOISE
codedb_find 2843 2780 -2.22% -63 OK
codedb_hot 26176 25757 -1.60% -419 OK
codedb_outline 18907 15963 -15.57% -2944 OK
codedb_read 13474 13618 +1.07% +144 OK
codedb_search 46667 44166 -5.36% -2501 OK
codedb_snapshot 67496 78286 +15.99% +10790 NOISE
codedb_status 10279 9076 -11.70% -1203 OK
codedb_symbol 38748 39778 +2.66% +1030 OK
codedb_tree 28616 22183 -22.48% -6433 OK
codedb_word 12953 12356 -4.61% -597 OK

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if (std.Thread.spawn(.{}, statScanForInitial, .{ io, store, dir, recs, &build_err })) |t| {

P2 Badge Use per-worker error slots for parallel stat scans

When the initial scan has at least 256 entries, every spawned statScanForInitial worker receives the same &build_err, and that helper reads/writes the pointed-to ?anyerror without any lock or atomic. If store.recordSnapshot returns an error in one worker while another worker is checking or writing this shared slot, the scan has a data race and can lose or corrupt the error path; the other threaded builders in this file avoid this by passing &build_errors[i] per worker and reducing after join.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Keep parallel workers limited to statting disjoint entry slices, then record snapshots in deterministic discovery order after every join. This removes shared worker error state and avoids concurrent sequence assignment.

Add sequential-vs-parallel snapshot sequence and size parity coverage.
@justrach

Copy link
Copy Markdown
Owner Author

Addressed the stat-scan review finding in 4ad082f. Workers now only stat disjoint entry slices; after all joins, Store.recordSnapshot runs serially in discovery order. This removes the shared build_err race, avoids concurrent sequence assignment/lock contention, and preserves failed-stat behavior. Added a forced 1-worker vs 4-worker regression asserting snapshot sequence and size parity. test-index is 181/181 and the full 23-step suite passes.

@github-actions

Copy link
Copy Markdown

Benchmark Regression Report

Thresholds: 10.00% and 50,000 ns absolute delta

NOISE means the percentage threshold was exceeded, but the absolute delta was too small to fail CI.

Tool Base (ns) Head (ns) Delta Abs Delta (ns) Status
codedb_bundle 65885 62021 -5.86% -3864 OK
codedb_changes 10756 10909 +1.42% +153 OK
codedb_context 293990 298964 +1.69% +4974 OK
codedb_deps 296 399 +34.80% +103 NOISE
codedb_edit 39567 40315 +1.89% +748 OK
codedb_find 2792 2921 +4.62% +129 OK
codedb_hot 25655 26843 +4.63% +1188 OK
codedb_outline 19017 16040 -15.65% -2977 OK
codedb_read 17335 15406 -11.13% -1929 OK
codedb_search 47245 52676 +11.50% +5431 NOISE
codedb_snapshot 65961 68785 +4.28% +2824 OK
codedb_status 11436 9489 -17.03% -1947 OK
codedb_symbol 45259 38444 -15.06% -6815 OK
codedb_tree 29505 20888 -29.21% -8617 OK
codedb_word 12563 14265 +13.55% +1702 NOISE

@justrach
justrach merged commit 6253eeb into release/0.2.5829 Jul 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants